
#include <iostream>
#include <sstream>
#include <iomanip>
#include <string>
#include <vector>

#include <boost/filesystem.hpp>
#include <boost/program_options.hpp>
namespace po = boost::program_options;

#include <boost/date_time/posix_time/posix_time.hpp>
using namespace boost::posix_time;

#include "bocat/common/logging.hpp"
#include "bocat/common/md5.hpp"

#include "binindex.hpp"

using namespace std;

#define	DEFAULT_TIMES	32

#define	N_ROUND	100000

static bocat::common::Logger*   logger_p = NULL;

#define INFO(ARGS...)   logger_p->info(__LINE__,__FILE__,ARGS)
#define ERROR(ARGS...)  logger_p->error(__LINE__,__FILE__,ARGS)

float
getTimeDuration( ptime time_begin, ptime time_end )
{
	time_period	tp( time_begin, time_end );
	time_duration	td = tp.length();
	long	time_last_ms = td.total_milliseconds();
	float	time_last_s = time_last_ms / 1000.0f;

	return time_last_s;
}




void	test_001( const std::string& ini_file, const std::string& ini_section, int times, int key_size )
{
	fekcab::BinIndex	bi( ini_file.c_str(), ini_section.c_str() );

	// test PUT
	{
		INFO( "testing PUT ..." );
		ptime	time_start = microsec_clock::local_time();
		ptime	time_start_R = time_start;
		u_int32_t	count_down_R = N_ROUND;
		u_int32_t	count_round = 0;

		for (int i=0; i<times; i++) {
			ostringstream	os_key;      os_key << "KEY--" << setw(8) << setfill('0') << i;
			string	key = os_key.str();
			
            ostringstream   os_data;     os_data << "D-" << setw(10) << setfill('0') << i;
            string  data = os_data.str();

			bocat::common::MD5	m;
			m.update( key.c_str(), key.size() );
			string	skey( m.hexdigest(), key_size );

			//std::cout << "PUT skey " << skey << std::endl;
			int rc = bi.put( skey, data );
			if ( rc < 0 ) {
				ERROR( "fail to PUT KEY: %s", key.c_str() );
				return;
			}

			--count_down_R;
			if (0 == count_down_R) {
				ptime   time_stop_R = microsec_clock::local_time();
				float   time_last_s = getTimeDuration( time_start_R, time_stop_R );
				float   tps = N_ROUND / time_last_s;
				INFO( "  R-PUT %u*%u records last %.2f seconds, tps=%.2f", ++count_round, N_ROUND, time_last_s, tps );

				time_start_R = time_stop_R;
				count_down_R = N_ROUND;
			}
		}

		ptime	time_stop = microsec_clock::local_time();
		float	time_last_s = getTimeDuration( time_start, time_stop );
		float	tps = times / time_last_s;
		INFO( "    PUT %u records last %.2f seconds, tps=%.2f", times, time_last_s, tps );
	}

	// test GET
	{
		INFO( "testing GET ..." );
		ptime	time_start = microsec_clock::local_time();
		ptime	time_start_R = time_start;
		u_int32_t	count_down_R = N_ROUND;
		u_int32_t	count_round = 0;

		for (int i=0; i<times; i++) {
			ostringstream	os_key;      os_key << "KEY--" << setw(8) << setfill('0') << i;
			string	key = os_key.str();

            ostringstream   os_data;     os_data << "D-" << setw(10) << setfill('0') << i;
            string  data_want = os_data.str();

			bocat::common::MD5	m;
			m.update( key.c_str(), key.size() );
			string	skey( m.hexdigest(), key_size );

            string  data;
			bi.get( skey, data );
			if (data.size() < 1) {
				ERROR( "fail to get KEY: %s", key.c_str() );
				return;
			}
			if (data != data_want) {
			//if (memcmp(data.c_str(), data_want.c_str(), data_want.size())) {
				ERROR( "wrong data for KEY: ", key.c_str());
				ERROR( "      GOT  : '%s'", data.c_str() );
				ERROR( "	  WANT : '%s'", data_want.c_str() );
				return;
			}


			--count_down_R;
			if (0 == count_down_R) {
				ptime   time_stop_R = microsec_clock::local_time();
				float   time_last_s = getTimeDuration( time_start_R, time_stop_R );
				float   tps = N_ROUND / time_last_s;
				INFO( "  R-GET %u*%u records last %.2f seconds, tps=%.2f", ++count_round, N_ROUND, time_last_s, tps );

				time_start_R = time_stop_R;
				count_down_R = N_ROUND;
			}
		}

		ptime	time_stop = microsec_clock::local_time();
		float	time_last_s = getTimeDuration( time_start, time_stop );
		float	tps = times / time_last_s;
		INFO( "    GET %u records last %.2f seconds, tps=%.2f", times, time_last_s, tps );
	}

	// test ERASE
	{
		INFO( "testing ERASE ..." );
		ptime	time_start = microsec_clock::local_time();
		ptime	time_start_R = time_start;
		u_int32_t	count_down_R = N_ROUND;
		u_int32_t	count_round = 0;

		for (int i=0; i<times; i++) {
			ostringstream	os_key;      os_key << "KEY--" << setw(8) << setfill('0') << i;
			string	key = os_key.str();

			bocat::common::MD5	m;
			m.update( key.c_str(), key.size() );
			string	skey( m.hexdigest(), key_size );

			bi.erase( skey );

			--count_down_R;
			if (0 == count_down_R) {
				ptime   time_stop_R = microsec_clock::local_time();
				float   time_last_s = getTimeDuration( time_start_R, time_stop_R );
				float   tps = N_ROUND / time_last_s;
				INFO( "  R-ERASE %u*%u records last %.2f seconds, tps=%.2f", ++count_round, N_ROUND, time_last_s, tps );

				time_start_R = time_stop_R;
				count_down_R = N_ROUND;
			}
		}

		ptime	time_stop = microsec_clock::local_time();
		float	time_last_s = getTimeDuration( time_start, time_stop );
		float	tps = times / time_last_s;
		INFO( "  ERASE %u records last %.2f seconds, tps=%.2f", times, time_last_s, tps );
	}

}


int	main(int argc, char* argv[])
{
	std::string	ini_file;
	std::string	ini_section;
	int 	times = 0;
	bool	verbose = false;
	int		key_size = 0;

    try {

        po::options_description desc("Allowed options");
        desc.add_options()
            ("help,h", "produce help message")
            ("verbose,v", "output verbose message")
			("times", po::value<int>(&times)->default_value(DEFAULT_TIMES), "number of test to execute")
			("key-size", po::value<int>(&key_size)->default_value(32), "size of key in bytes (32-bytes md5 string)")
            ("ini-file", po::value<std::string>(&ini_file)->default_value("index.ini"), "INI configure filename")
            ("ini-section", po::value<std::string>(&ini_section)->default_value("fekcab::binindex"), "INI configure section")

        ;   

        po::variables_map vm;    
        po::store(po::parse_command_line(argc, argv, desc), vm);
        po::notify(vm);    

        if (vm.count("help")) {
            std::cout << desc << "\n";
            return 1;
        }   

		if (vm.count("verbose")) {
			verbose = true;
		}
    }   
    catch(std::exception& e) {
        std::cerr << "error: " << e.what() << "\n";
        return 1;
    }   
    catch(...) {
        std::cerr << "Exception of unknown type!\n";
		return 2;
    }   

	
	if ( key_size > 32 ) {
		key_size = 32;
	}

    logger_p = bocat::common::getLogger();

	test_001( ini_file, ini_section, times, key_size );


	return	0;
}

